FIxed the reponse type of all Middleware & controller all over the middleware and controller - #253
Conversation
📝 WalkthroughWalkthroughControllers and middlewares were changed to propagate errors via ChangesCentralized Error Handling and Responses
Sequence Diagram(s)sequenceDiagram
participant Route
participant Controller
participant ErrorMiddleware
Route->>Controller: call(req,res,next)
Controller-->>Route: ApiResponse.send(res)
Controller-->>ErrorMiddleware: next(AppError)
ErrorMiddleware-->>Route: res.status(err.statusCode).json({success:false,error,message})
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (9)
apps/public-api/src/controllers/data.controller.js (1)
218-230:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winUnresolved merge conflict will break the build.
Lines 218-230 contain unresolved merge conflict markers (
<<<<<<< Updated upstream,=======,>>>>>>> Stashed changes) that prevent the code from parsing. The CI pipeline confirms Jest cannot load this module.Accept the stashed changes (lines 228-229) which use
AppErrorand are consistent with the PR's standardization objectives and coding guidelines, then remove the conflict markers and the upstream version (lines 220-226).🔧 Proposed resolution
-<<<<<<< Updated upstream - - if (!collectionConfig) { - return res.status(404).json({ - success: false, - data: {}, - message: "Collection not found", - }); - } -======= if (!collectionConfig) return next(new AppError(404, "Collection not found")); ->>>>>>> Stashed changesAs per coding guidelines, all API endpoints must use AppError for errors and never use raw
res.status().json()responses.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/controllers/data.controller.js` around lines 218 - 230, Remove the Git conflict markers and the upstream branch block and keep the stashed change that uses AppError: replace the entire conflicted block (the lines with <<<<<<<, =======, and >>>>>>> plus the res.status(...) branch) with the single statement that calls next(new AppError(404, "Collection not found")); ensure you reference the existing collectionConfig check and the next/AppError usage so the endpoint follows the project's error-handling convention and no raw res.status().json() remains.apps/dashboard-api/src/controllers/userAuth.controller.js (2)
345-370:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
getAuthCollectionis undefined in this controller.Both
resetPasswordUserandupdateProfilecallgetAuthCollection(project), but this file neither defines nor imports that symbol. These paths will throw aReferenceErrorbefore reaching Mongo.Also applies to: 379-406
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/userAuth.controller.js` around lines 345 - 370, The controller is calling getAuthCollection in resetPasswordUser and updateProfile but that symbol is not defined or imported; add a proper import or require for getAuthCollection (the same function used elsewhere to obtain the auth collection) at the top of this file and ensure the imported name matches the one used in resetPasswordUser and updateProfile, or alternatively define a local wrapper that calls the shared getAuthCollection implementation so the calls to getAuthCollection(project) resolve without throwing ReferenceError.
64-116:⚠️ Potential issue | 🟠 Major | ⚡ Quick winThe dashboard user-auth success contract is still inconsistent.
These updated endpoints still return bare tokens, raw user objects,
{ sessions }, or{ message }payloads, so callers cannot rely on the standardized{ success, data, message }shape after this refactor.
As per coding guidelines, "All API endpoints must return response format:{ success: bool, data: {}, message: "" }."Also applies to: 127-150, 159-183, 195-229, 277-307, 316-337, 345-370, 379-406, 414-451, 460-477, 484-512, 519-539, 546-563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/userAuth.controller.js` around lines 64 - 116, The signup handler (module.exports.signup) currently sends a bare JSON body (token, message, userId); update its final res.status(201).json call to use the standard response envelope { success: true, data: { token, userId }, message: "User registered successfully. Please verify your email." } and do the same for all other handlers in this controller (e.g., the other exported auth methods referenced in the review) by replacing any raw or inconsistent responses (bare tokens, raw user objects, { sessions }, { message }) with the { success, data, message } shape so callers can rely on a consistent contract.apps/dashboard-api/src/controllers/auth.controller.js (1)
295-315:⚠️ Potential issue | 🟠 Major | ⚡ Quick winThese updated auth handlers still return mixed success schemas.
register, thesendTokenResponse-based paths, andgetMestill emit legacy bodies like{ message: ... }or{ success: true, user: ... }instead of a single{ success, data, message }envelope, so this refactor does not actually standardize the dashboard auth contract.
As per coding guidelines, "All API endpoints must return response format:{ success: bool, data: {}, message: "" }."Also applies to: 319-335, 479-499, 613-622
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/auth.controller.js` around lines 295 - 315, The register handler and other auth responses (including sendTokenResponse-based flows and getMe) return legacy bodies and must be changed to the standardized envelope { success: boolean, data: {...}, message: "" }; update register (function register) to return res.status(201).json({ success: true, data: { userId: newDev._id } or { user: sanitizedUser }, message: "Registered successfully" }) instead of { message: ... }, and update failure responses to use { success: false, data: {}, message: "..." } where appropriate; refactor the sendTokenResponse helper to emit the same envelope (success/data/message) and adjust all callers (the sendTokenResponse-based paths) to stop returning { success: true, user: ... } so getMe and other handlers return { success, data, message } consistently; ensure any Zod/AppError branches also respond via next(...) or the envelope format so all auth endpoints conform to the single contract.apps/public-api/src/__tests__/userAuth.social.test.js (1)
462-535:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winResolve this merge conflict and keep the atomic
GETDELbranch.The conflict markers make the file unparsable, which is why CI dies before any tests run. The
redis.get()+redis.del()alternative is also the wrong behavior here for one-time exchange codes: two requests can read the same code before either delete lands.As per coding guidelines, "Social auth refresh exchange must use Redis key pattern
project:social-auth:refresh-exchange:{rtCode}with atomicGETDELfor concurrency safety."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/userAuth.social.test.js` around lines 462 - 535, Remove the merge conflict markers and restore the atomic GETDEL branch: in the tests for controller.exchangeSocialRefreshToken (file userAuth.social.test.js) delete the conflict blocks and keep the assertions that reference redis.getdel (not redis.get + redis.del), ensure calls to exchangeSocialRefreshToken still pass next where appropriate, and retain the expectations that check res.status/res.json responses for success and error cases (and the test that uses redis.getdel.mockResolvedValueOnce(...) for payload mismatch). Also remove the alternative redis.get/redis.del assertions and the next/AppError-style expectations introduced by the other branch so the tests reflect the atomic GETDEL behavior.apps/dashboard-api/src/controllers/analytics.controller.js (1)
42-46:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse the same owner filter for the follow-up project lookup.
totalProjectsis computed with both{ owner: user_id }and{ owner: userId }, but the laterProject.find()only uses the string form. Ifowneris stored as anObjectId,totalProjectswill be correct whiletotalRequests,totalWebhooks, andtotalUsersare all understated.Suggested fix
const [stats, dev] = await Promise.all([ + const ownerFilter = { + $or: [ + { owner: user_id }, + { owner: userId } + ] + }; + Project.aggregate([ - { - $match: { - $or: [ - { owner: user_id }, - { owner: userId } - ] - } - }, + { $match: ownerFilter }, { $group: { _id: null, @@ - const projects = await Project.find({ owner: user_id }).select("_id").lean(); + const projects = await Project.find(ownerFilter).select("_id").lean();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/analytics.controller.js` around lines 42 - 46, The follow-up Project.find used to build projectIds uses a different owner variable/type than earlier (user_id vs userId) causing mismatched filters and understating totalRequests/totalWebhooks/totalUsers; update the Project.find call that produces projects (and any subsequent queries that use its results) to use the same owner filter/type as used when computing totalProjects (e.g., use owner: userId or convert userId to an ObjectId via mongoose.Types.ObjectId(userId)) so Project.find, projects.map, totalRequests, totalWebhooks, and totalUsers all filter by the identical owner value/type.apps/public-api/src/__tests__/userAuth.refresh.test.js (1)
221-248:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winKeep this refresh error-path on
next(AppError)like the rest of the suite.This test still exercises the old direct
res.status/res.jsonbranch. IfrefreshToken()now forwards soft-deleted-user failures throughnext, this case will either fail incorrectly or keep validating the pre-refactor contract instead of the new one.Suggested fix
- await controller.refreshToken(req, res); - - expect(res.status).toHaveBeenCalledWith(403); - expect(res.json).toHaveBeenCalledWith({ success: false, data: {}, message: expect.stringContaining('deletion') }); + await controller.refreshToken(req, res, next); + + expect(next).toHaveBeenCalledWith(expect.any(AppError)); + expect(next.mock.calls[next.mock.calls.length - 1][0].statusCode).toBe(403); expect(res.clearCookie).toHaveBeenCalledWith('refreshToken', expect.any(Object));As per coding guidelines, "All API endpoints must return response format:
{ success: bool, data: {}, message: "" }. Use AppError class for errors. Never use raw throw statements or expose MongoDB errors to clients."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/userAuth.refresh.test.js` around lines 221 - 248, The test currently asserts direct res.status/res.json behavior for soft-deleted users but the refreshed controller.refreshToken now forwards errors via next(AppError); update the test to call controller.refreshToken(req, res, next) and assert next was called with an AppError (check instance/type and that it contains status 403 and a message containing "deletion"), assert res.status/res.json were not used, and still assert res.clearCookie was called with 'refreshToken'; keep existing mocks (getRefreshSession, Project.__chain.lean, mockModel.findOne) and only change the assertions and invocation to validate the next(AppError) error path.apps/dashboard-api/src/controllers/project.controller.js (1)
304-305:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftFinish the response-envelope migration in this controller.
These handlers still mix bare arrays/objects, inline
{ error: ... }payloads, and partial{ success: ... }bodies. That leaves dashboard clients with incompatible schemas depending on which project route they call, which defeats the contract this PR is introducing.As per coding guidelines,
apps/dashboard-api/src/controllers/**/*.js: "All API endpoints must return response format:{ success: bool, data: {}, message: "" }. Use AppError class for errors. Never use raw throw statements or expose MongoDB errors to clients."Also applies to: 385-385, 469-469, 560-566, 591-601, 621-634, 1277-1277, 1327-1327, 1372-1372, 2059-2062, 2075-2086, 2105-2152, 2354-2358, 2430-2433, 2501-2507, 2538-2538, 2567-2567
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 304 - 305, The controller returns bare objects/arrays and inline error payloads; update all handlers in project.controller.js to use the standardized response envelope { success: boolean, data: object, message: string } (for example replace res.status(201).json(projectObj) with res.status(201).json({ success: true, data: projectObj, message: 'Project created' })), and convert all catch blocks to propagate errors via the AppError class (or call next(new AppError(...))) instead of sending raw MongoDB/error objects or throwing raw errors; search for occurrences of projectObj, any direct res.json/res.status(...).json({...}) that return arrays/objects or { error: ... } / { success: ... } and normalize them to the envelope and use AppError for error cases across the listed ranges.apps/dashboard-api/src/controllers/billing.controller.js (1)
183-195:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard Razorpay webhook signature length before
crypto.timingSafeEqual(avoid length-mismatch exceptions).
crypto.timingSafeEqualthrows when the two Buffers have different lengths, so the handler can fail before returning the intendednext(new AppError(401, 'Invalid webhook signature.')). Add an explicit length check before callingtimingSafeEqualinapps/dashboard-api/src/controllers/billing.controller.jsaround thex-razorpay-signaturecomparison.Suggested fix
const rawBody = req.rawBody || JSON.stringify(req.body); const expectedSignature = crypto .createHmac('sha256', webhookSecret) .update(rawBody) .digest('hex'); - if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { + const actualBuffer = Buffer.from(String(signature)); + const expectedBuffer = Buffer.from(expectedSignature); + if ( + actualBuffer.length !== expectedBuffer.length || + !crypto.timingSafeEqual(actualBuffer, expectedBuffer) + ) { return next(new AppError(401, 'Invalid webhook signature.')); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/billing.controller.js` around lines 183 - 195, The webhook signature comparison can throw if Buffer lengths differ; before calling crypto.timingSafeEqual, explicitly check that Buffer.byteLength(signature) (or Buffer.from(signature).length) equals Buffer.byteLength(expectedSignature) (or Buffer.from(expectedSignature).length) and if not, return next(new AppError(401, 'Invalid webhook signature.')); update the block around the variables signature, expectedSignature and the crypto.timingSafeEqual call to perform this length check and only call timingSafeEqual when lengths match.
🧹 Nitpick comments (3)
apps/dashboard-api/src/controllers/webhook.controller.js (1)
59-70: ⚡ Quick winSuccess responses not standardized in this controller.
The error paths were migrated to
AppError, but the success responses across this file (Lines 59-70, 110, 145, 211, 261, 307, 415) still emit bareres.json({ message, data })without thesuccessflag. Sibling controllers in this PR (e.g.storage.controller.js) adoptApiResponse(...).send(res, status). Aligning these would complete the standardization goal of the PR.As per coding guidelines, "All API endpoints must return response format:
{ success: bool, data: {}, message: "" }."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/webhook.controller.js` around lines 59 - 70, Replace the ad-hoc success responses in webhook.controller.js with the standardized ApiResponse pattern: for each place that currently calls res.json(...) or res.status(...).json(...), construct an ApiResponse({ success: true, message: "<same message>", data: { ... } }) and call .send(res, <statusCode>) instead; specifically update the response around the webhook object (the block returning _id, projectId, name, url, events: Object.fromEntries(webhook.events || new Map()), enabled, createdAt) and the other success sites referenced (around lines 110, 145, 211, 261, 307, 415) so they all use ApiResponse(...).send(res, status) and preserve the same data payloads and HTTP status codes. Ensure you import/require ApiResponse if not already present.apps/public-api/src/__tests__/aggregation.test.js (1)
6-7: ⚡ Quick winAlign the
ApiResponse.send()mock signature with the real helper (consistency only).
aggregateDatainapps/public-api/src/controllers/data.controller.jscallssend(res, 200)explicitly, so this mock’s lack of a default won’t affect the success-pathres.statusCode === 200assertions inapps/public-api/src/__tests__/aggregation.test.js(lines 6-7). Still, matching the real helper prevents future mismatches.Suggested fix
- ApiResponse: class ApiResponse { constructor(d, m) { this.data=d; this.message=m; this.success=true; } send(res, code) { return res.status(code).json({ success: this.success, data: this.data, message: this.message }); } }, + ApiResponse: class ApiResponse { constructor(d, m) { this.data=d; this.message=m; this.success=true; } send(res, code = 200) { return res.status(code).json({ success: this.success, data: this.data, message: this.message }); } },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/aggregation.test.js` around lines 6 - 7, The ApiResponse mock's send method should match the real helper by accepting an optional status code defaulting to 200; update the ApiResponse class in the test so send(res, code = 200) uses the provided code or defaults to 200 and returns res.status(code).json(...), ensuring compatibility with aggregateData which calls send(res, 200) and preventing future signature mismatches.apps/public-api/src/__tests__/userAuth.email.test.js (1)
312-314: ⚡ Quick winAdditional success assertions should verify standardized format.
Several success-case assertions either don't validate the response payload at all (lines 456, 540, 564) or check for top-level
message(lines 312-314, 487-489) instead of the standardized{ success, data, message }structure. Consider strengthening these assertions to verify compliance with the response format guidelines.Also applies to: 456-457, 487-489, 540-541, 564-565
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/userAuth.email.test.js` around lines 312 - 314, Update the success-case assertions to validate the standardized response shape { success, data, message } instead of only checking top-level message; for each test that currently calls expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ message: expect.any(String) })) (and the other weak checks at the mentioned locations), assert expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ success: true, data: expect.anything(), message: expect.any(String) })) or adjust data to expect.any(Object) / null as appropriate for that endpoint; locate and update the assertions around res.json and any expect.objectContaining usages in the tests named in userAuth.email.test.js to ensure uniform response format validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/dashboard-api/src/__tests__/billing.controller.test.js`:
- Around line 178-180: Replace the undefined AppError references in the
assertions with the mockAppError used elsewhere: update the two expectations
that call expect.any(AppError) and any direct AppError references (e.g.,
expect(next).toHaveBeenCalledWith(expect.any(AppError)) and
expect(next.mock.calls[0][0].statusCode).../message...) to use mockAppError
instead (e.g., expect.any(mockAppError)), keeping the rest of the checks on next
and next.mock.calls intact so they reference the existing mockAppError symbol.
In `@apps/dashboard-api/src/app.js`:
- Around line 158-162: The 500 fallback handler is leaking internal details by
returning err.message; update the error-response in the Express error middleware
(the handler that receives err, req, res) to remove err.message and return only
a fixed public message (e.g. "Internal Server Error" or the existing "Something
went wrong!") and success:false; keep logging the full error (err.stack)
server-side for diagnostics and, if desired, generate/return a safe errorId or
correlation id instead of the raw exception message.
In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js`:
- Around line 142-143: The early-return when cohortSize === 0 returns an
ApiResponse missing d1Pct, d7Pct, and d30Pct; keep the response shape identical
to the non-empty path by including d1Pct: 0, d7Pct: 0, and d30Pct: 0 alongside
month, cohortSize: 0, d1, d7, and d30 in the ApiResponse sent from that branch
(the code constructing the ApiResponse at the cohortSize check).
- Around line 115-116: The current month validation in the controller (the if
that checks month and uses /^\d{4}-\d{2}$/) allows invalid months like 00 or 13;
update that check to reject months outside 01–12 by using a stricter pattern
(e.g. /^\d{4}-(0[1-9]|1[0-2])$/) or parse the captured month and assert 1 <=
month <= 12 before returning new AppError(400, ...). Locate the validation
around the month variable in admin.metrics.controller.js and replace the
regex/condition so invalid month values trigger the AppError.
In `@apps/dashboard-api/src/controllers/auth.controller.js`:
- Around line 591-609: The refreshToken controller currently maps all exceptions
to a 403 "Invalid or expired refresh token"; change the error handling so only
JWT verification errors are converted to an AppError(403) and all other errors
are forwarded to the global handler with next(err). Specifically, in
module.exports.refreshToken around jwt.verify and Developer.findById, detect jwt
errors (e.g. err.name === 'TokenExpiredError' or 'JsonWebTokenError') and call
next(new AppError(403, "Invalid or expired refresh token")), but for any other
error (DB lookup, persistence, unexpected exceptions) call next(err) so they
surface as 5xx; keep references to jwt.verify, Developer.findById,
sendTokenResponse, AppError, and next when implementing this conditional catch
behavior.
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 2459-2463: The code currently treats 'owner-write-only' as an
allowed RLS mode (see validMode and allowedModes) and returns an error only for
unknown values; instead normalize legacy 'owner-write-only' to 'public-read'
before validation and persistence: when computing validMode (from mode or
collection?.rls?.mode), if the value equals 'owner-write-only' set it to
'public-read', then validate against allowedModes = new
Set(['public-read','private']) and proceed; update the same normalization in the
other block handling RLS (the later code that uses validMode/allowedModes) so
legacy inputs are converted rather than stored.
- Around line 411-416: The file contains unresolved Git conflict markers
(<<<<<<<, =======, >>>>>>>) across several handlers (getSingleProject,
createCollection, getData, toggleAuth, updateAuthProviders, updateCollectionRls
and others) which breaks parsing; open project.controller.js, remove all
conflict markers and decide the correct behavior for each conflicted block
(e.g., prefer centralized error handling via next(new AppError(...)) OR direct
res.status(...).json(...) consistently), reconcile both sides accordingly in
each function (getSingleProject, createCollection, getData, toggleAuth,
updateAuthProviders, updateCollectionRls), keep only one implementation per
block, ensure imports/usage for AppError or res are consistent, run lint/tests
to confirm parsing and CI pass.
In `@apps/dashboard-api/src/controllers/release.controller.js`:
- Around line 71-72: Both getAllReleases and createRelease currently return raw
payloads (an array and { message, count }) instead of the standardized {
success, data, message } shape; update the handlers (e.g., the function using
Release.find() and the createRelease handler around lines noting create logic)
to always respond with res.json({ success: true, data: <payload>, message: "" })
and on failures use the AppError class (not raw throw or exposing Mongo/Mongoose
errors) to forward errors; ensure data contains the actual resource(s) (e.g.,
data: { releases } or data: { release, count }) so callers receive the
consistent schema.
In `@apps/public-api/src/__tests__/userAuth.email.test.js`:
- Around line 254-259: Update the test's success assertion to match the
standardized ApiResponse shape: instead of asserting top-level token and userId,
assert that res.json was called with an object containing success: true and data
that contains token: 'signed_access_token' and userId: 'user_123' (use
expect.objectContaining for both the outer object and the nested data). Locate
the assertion using res.json in this test and align it with the ApiResponse mock
defined earlier in the file.
- Line 109: The test is failing because the controller module has unresolved
merge conflict markers (e.g., <<<<<<<, =======, >>>>>>>) in the
userAuth.controller source; open the controller module, remove the conflict
markers and reconcile the conflicting blocks so the file is valid JavaScript,
ensure the exported symbols (module.exports or named exports used by tests) like
the userAuth controller functions remain correct and consistent after resolving,
save and run the tests to confirm the SyntaxError is gone.
In `@apps/public-api/src/controllers/userAuth.controller.js`:
- Line 1: This file contains unresolved Git merge-conflict markers (<<<<<<<,
=======, >>>>>>>) that break parsing; open the userAuth.controller.js source,
find all conflict regions beginning with "<<<<<<<" and ending with ">>>>>>>" and
choose the correct side (or manually merge both sides) for each conflict across
the occurrences noted (including the other ranges around lines ~1818 and ~3538),
remove the conflict markers, ensure the resulting exports/functions (e.g., any
controller functions or exported handlers in this file) are syntactically valid,
run a quick lint/build to confirm parsing succeeds, and commit the cleaned file.
- Around line 2679-2708: The exchangeSocialRefreshToken handler currently does
separate redis.get and redis.del calls which allow race conditions; update the
logic in exchangeSocialRefreshToken to perform an atomic read-and-delete using
Redis GETDEL (or the client's equivalent) when fetching
getSocialRefreshExchangeKey(rtCode) so the stored payload is retrieved and
removed in one operation, then validate parsedExchange.token and
parsedExchange.refreshToken and proceed as before; ensure you still handle JSON
parse errors and return the same AppError responses.
In `@docker-compose.yml`:
- Around line 8-9: The docker-compose.yml currently publishes datastore ports
(ports: '27017:27017' and similarly '6379:6379') to the host while NODE_ENV:
production is set; remove or restrict these host-facing port mappings for the
Mongo/Redis services: either delete the ports entries so they remain internal to
the Compose network, or change them to bind to localhost (e.g.,
127.0.0.1:27017:27017 and 127.0.0.1:6379:6379), and if host access is needed for
local debugging move the host-published mappings into a dev-only override file
(docker-compose.override.yml) instead; update the mongo/redis service blocks
that contain the ports keys and the specific strings '27017:27017' and
'6379:6379'.
In `@packages/common/src/middleware/checkAuthEnabled.js`:
- Around line 17-21: The middleware currently checks usersCollection.model
entries with strict checks (f.type === 'String' and f.required) which rejects
valid schemas that use lowercase 'string' or truthy numeric/string required
flags; update the check in checkAuthEnabled.js to normalize each field like the
dashboard's project.controller.validateUsersSchema() (treat type
case-insensitively, e.g., 'string' or 'String' as String, and treat required as
truthy for '1', 1, 'true', true, etc.) before testing for email and password
presence; specifically normalize entries from usersCollection.model, then locate
the email and password fields using the normalized.type === 'String' and
normalized.required truthiness and return the same AppError only if either
normalized field is missing.
In `@packages/common/src/middleware/loadProjectForAdmin.js`:
- Around line 7-18: In the loadProjectForAdmin middleware, validate projectId
(use mongoose.Types.ObjectId.isValid or equivalent) before calling
Project.findOne and return next(new AppError(400, "Invalid project ID")) for an
invalid id; then keep the existing owner-restricted query (Project.findOne({_id:
projectId, owner: req.user._id})) and assignment to req.project. In the catch
block, stop echoing err.message to clients—call next(new AppError(500, "Internal
Server Error")) and log the original err internally (e.g., console.error or
req.logger) instead of returning err.message.
In `@packages/common/src/middleware/standardizeApiResponse.js`:
- Around line 70-78: The current fallback replaces a parsed string message from
toErrorMessage(body) with raw body.error objects/arrays; update the conditional
logic in the standardizeApiResponse middleware so you only assign
errorTitle/errorMessage from body.error when body.error is a string (i.e., keep
the existing checks `if (body.error && typeof body.error === 'string')` and do
not set errorMessage from body.error when it is an object/array), and ensure the
previously computed toErrorMessage(body) result remains the message when
body.error is non-string; reference the variables and helpers errorTitle,
errorMessage, body.error and the toErrorMessage function to locate and change
the fallback logic.
---
Outside diff comments:
In `@apps/dashboard-api/src/controllers/analytics.controller.js`:
- Around line 42-46: The follow-up Project.find used to build projectIds uses a
different owner variable/type than earlier (user_id vs userId) causing
mismatched filters and understating totalRequests/totalWebhooks/totalUsers;
update the Project.find call that produces projects (and any subsequent queries
that use its results) to use the same owner filter/type as used when computing
totalProjects (e.g., use owner: userId or convert userId to an ObjectId via
mongoose.Types.ObjectId(userId)) so Project.find, projects.map, totalRequests,
totalWebhooks, and totalUsers all filter by the identical owner value/type.
In `@apps/dashboard-api/src/controllers/auth.controller.js`:
- Around line 295-315: The register handler and other auth responses (including
sendTokenResponse-based flows and getMe) return legacy bodies and must be
changed to the standardized envelope { success: boolean, data: {...}, message:
"" }; update register (function register) to return res.status(201).json({
success: true, data: { userId: newDev._id } or { user: sanitizedUser }, message:
"Registered successfully" }) instead of { message: ... }, and update failure
responses to use { success: false, data: {}, message: "..." } where appropriate;
refactor the sendTokenResponse helper to emit the same envelope
(success/data/message) and adjust all callers (the sendTokenResponse-based
paths) to stop returning { success: true, user: ... } so getMe and other
handlers return { success, data, message } consistently; ensure any Zod/AppError
branches also respond via next(...) or the envelope format so all auth endpoints
conform to the single contract.
In `@apps/dashboard-api/src/controllers/billing.controller.js`:
- Around line 183-195: The webhook signature comparison can throw if Buffer
lengths differ; before calling crypto.timingSafeEqual, explicitly check that
Buffer.byteLength(signature) (or Buffer.from(signature).length) equals
Buffer.byteLength(expectedSignature) (or Buffer.from(expectedSignature).length)
and if not, return next(new AppError(401, 'Invalid webhook signature.')); update
the block around the variables signature, expectedSignature and the
crypto.timingSafeEqual call to perform this length check and only call
timingSafeEqual when lengths match.
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 304-305: The controller returns bare objects/arrays and inline
error payloads; update all handlers in project.controller.js to use the
standardized response envelope { success: boolean, data: object, message: string
} (for example replace res.status(201).json(projectObj) with
res.status(201).json({ success: true, data: projectObj, message: 'Project
created' })), and convert all catch blocks to propagate errors via the AppError
class (or call next(new AppError(...))) instead of sending raw MongoDB/error
objects or throwing raw errors; search for occurrences of projectObj, any direct
res.json/res.status(...).json({...}) that return arrays/objects or { error: ...
} / { success: ... } and normalize them to the envelope and use AppError for
error cases across the listed ranges.
In `@apps/dashboard-api/src/controllers/userAuth.controller.js`:
- Around line 345-370: The controller is calling getAuthCollection in
resetPasswordUser and updateProfile but that symbol is not defined or imported;
add a proper import or require for getAuthCollection (the same function used
elsewhere to obtain the auth collection) at the top of this file and ensure the
imported name matches the one used in resetPasswordUser and updateProfile, or
alternatively define a local wrapper that calls the shared getAuthCollection
implementation so the calls to getAuthCollection(project) resolve without
throwing ReferenceError.
- Around line 64-116: The signup handler (module.exports.signup) currently sends
a bare JSON body (token, message, userId); update its final res.status(201).json
call to use the standard response envelope { success: true, data: { token,
userId }, message: "User registered successfully. Please verify your email." }
and do the same for all other handlers in this controller (e.g., the other
exported auth methods referenced in the review) by replacing any raw or
inconsistent responses (bare tokens, raw user objects, { sessions }, { message
}) with the { success, data, message } shape so callers can rely on a consistent
contract.
In `@apps/public-api/src/__tests__/userAuth.refresh.test.js`:
- Around line 221-248: The test currently asserts direct res.status/res.json
behavior for soft-deleted users but the refreshed controller.refreshToken now
forwards errors via next(AppError); update the test to call
controller.refreshToken(req, res, next) and assert next was called with an
AppError (check instance/type and that it contains status 403 and a message
containing "deletion"), assert res.status/res.json were not used, and still
assert res.clearCookie was called with 'refreshToken'; keep existing mocks
(getRefreshSession, Project.__chain.lean, mockModel.findOne) and only change the
assertions and invocation to validate the next(AppError) error path.
In `@apps/public-api/src/__tests__/userAuth.social.test.js`:
- Around line 462-535: Remove the merge conflict markers and restore the atomic
GETDEL branch: in the tests for controller.exchangeSocialRefreshToken (file
userAuth.social.test.js) delete the conflict blocks and keep the assertions that
reference redis.getdel (not redis.get + redis.del), ensure calls to
exchangeSocialRefreshToken still pass next where appropriate, and retain the
expectations that check res.status/res.json responses for success and error
cases (and the test that uses redis.getdel.mockResolvedValueOnce(...) for
payload mismatch). Also remove the alternative redis.get/redis.del assertions
and the next/AppError-style expectations introduced by the other branch so the
tests reflect the atomic GETDEL behavior.
In `@apps/public-api/src/controllers/data.controller.js`:
- Around line 218-230: Remove the Git conflict markers and the upstream branch
block and keep the stashed change that uses AppError: replace the entire
conflicted block (the lines with <<<<<<<, =======, and >>>>>>> plus the
res.status(...) branch) with the single statement that calls next(new
AppError(404, "Collection not found")); ensure you reference the existing
collectionConfig check and the next/AppError usage so the endpoint follows the
project's error-handling convention and no raw res.status().json() remains.
---
Nitpick comments:
In `@apps/dashboard-api/src/controllers/webhook.controller.js`:
- Around line 59-70: Replace the ad-hoc success responses in
webhook.controller.js with the standardized ApiResponse pattern: for each place
that currently calls res.json(...) or res.status(...).json(...), construct an
ApiResponse({ success: true, message: "<same message>", data: { ... } }) and
call .send(res, <statusCode>) instead; specifically update the response around
the webhook object (the block returning _id, projectId, name, url, events:
Object.fromEntries(webhook.events || new Map()), enabled, createdAt) and the
other success sites referenced (around lines 110, 145, 211, 261, 307, 415) so
they all use ApiResponse(...).send(res, status) and preserve the same data
payloads and HTTP status codes. Ensure you import/require ApiResponse if not
already present.
In `@apps/public-api/src/__tests__/aggregation.test.js`:
- Around line 6-7: The ApiResponse mock's send method should match the real
helper by accepting an optional status code defaulting to 200; update the
ApiResponse class in the test so send(res, code = 200) uses the provided code or
defaults to 200 and returns res.status(code).json(...), ensuring compatibility
with aggregateData which calls send(res, 200) and preventing future signature
mismatches.
In `@apps/public-api/src/__tests__/userAuth.email.test.js`:
- Around line 312-314: Update the success-case assertions to validate the
standardized response shape { success, data, message } instead of only checking
top-level message; for each test that currently calls
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ message:
expect.any(String) })) (and the other weak checks at the mentioned locations),
assert expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ success:
true, data: expect.anything(), message: expect.any(String) })) or adjust data to
expect.any(Object) / null as appropriate for that endpoint; locate and update
the assertions around res.json and any expect.objectContaining usages in the
tests named in userAuth.email.test.js to ensure uniform response format
validation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c3d035d2-c7d6-4890-8f01-b69943695fae
📒 Files selected for processing (52)
apps/dashboard-api/src/__tests__/auth.controller.test.jsapps/dashboard-api/src/__tests__/authMiddleware.test.jsapps/dashboard-api/src/__tests__/auth_limiter.test.jsapps/dashboard-api/src/__tests__/billing.controller.test.jsapps/dashboard-api/src/__tests__/userAuth.controller.test.jsapps/dashboard-api/src/__tests__/webhook.controller.test.jsapps/dashboard-api/src/app.jsapps/dashboard-api/src/controllers/admin.metrics.controller.jsapps/dashboard-api/src/controllers/analytics.controller.jsapps/dashboard-api/src/controllers/auth.controller.jsapps/dashboard-api/src/controllers/billing.controller.jsapps/dashboard-api/src/controllers/events.controller.jsapps/dashboard-api/src/controllers/project.controller.jsapps/dashboard-api/src/controllers/release.controller.jsapps/dashboard-api/src/controllers/userAuth.controller.jsapps/dashboard-api/src/controllers/webhook.controller.jsapps/dashboard-api/src/middlewares/authMiddleware.jsapps/dashboard-api/src/middlewares/auth_limiter.jsapps/dashboard-api/src/middlewares/loadProjectForAdmin.jsapps/public-api/src/__tests__/aggregation.test.jsapps/public-api/src/__tests__/authorizeReadOperation.test.jsapps/public-api/src/__tests__/authorizeWriteOperation.test.jsapps/public-api/src/__tests__/data.controller.read.test.jsapps/public-api/src/__tests__/health.route.test.jsapps/public-api/src/__tests__/mail.controller.test.jsapps/public-api/src/__tests__/softDelete.test.jsapps/public-api/src/__tests__/storage.controller.test.jsapps/public-api/src/__tests__/userAuth.email.test.jsapps/public-api/src/__tests__/userAuth.refresh.test.jsapps/public-api/src/__tests__/userAuth.social.test.jsapps/public-api/src/__tests__/verifyApiKey.test.jsapps/public-api/src/app.jsapps/public-api/src/controllers/data.controller.jsapps/public-api/src/controllers/health.controller.jsapps/public-api/src/controllers/mail.controller.jsapps/public-api/src/controllers/schema.controller.jsapps/public-api/src/controllers/storage.controller.jsapps/public-api/src/controllers/userAuth.controller.jsapps/public-api/src/middlewares/authorizeReadOperation.jsapps/public-api/src/middlewares/authorizeWriteOperation.jsapps/public-api/src/middlewares/blockUsersCollectionDataAccess.jsapps/public-api/src/middlewares/projectRateLimiter.jsapps/public-api/src/middlewares/requireSecretKey.jsapps/public-api/src/middlewares/verifyApiKey.jsdocker-compose.ymlpackages/common/src/index.jspackages/common/src/middleware/checkAuthEnabled.jspackages/common/src/middleware/loadProjectForAdmin.jspackages/common/src/middleware/standardizeApiResponse.jspackages/common/src/middleware/verifyEmail.jspackages/common/src/utils/ApiResponse.jspackages/common/src/utils/AppError.js
| expect(next).toHaveBeenCalledWith(expect.any(AppError)); | ||
| expect(next.mock.calls[0][0].statusCode).toBe(401); | ||
| expect(next.mock.calls[0][0].message).toBe('Missing webhook signature.'); |
There was a problem hiding this comment.
AppError is undefined here — use mockAppError.
This file never imports or declares AppError; it defines mockAppError (Line 5) and exposes it through the mock as AppError: mockAppError (Line 25). Every other assertion in the suite uses expect.any(mockAppError). These two lines reference the bare AppError, which throws ReferenceError: AppError is not defined, exactly as reported by the failing CI job.
🐛 Proposed fix
- expect(next).toHaveBeenCalledWith(expect.any(AppError));
+ expect(next).toHaveBeenCalledWith(expect.any(mockAppError));
expect(next.mock.calls[0][0].statusCode).toBe(401);
expect(next.mock.calls[0][0].message).toBe('Missing webhook signature.');- expect(next).toHaveBeenCalledWith(expect.any(AppError));
+ expect(next).toHaveBeenCalledWith(expect.any(mockAppError));
expect(next.mock.calls[0][0].statusCode).toBe(401);Also applies to: 188-189
🧰 Tools
🪛 GitHub Actions: CI (Continuous Integration) / 4_dashboard-api-tests.txt
[error] 178-181: ReferenceError: AppError is not defined in expectation for Billing Controller 'returns 401 if signature is missing'.
🪛 GitHub Actions: CI (Continuous Integration) / dashboard-api-tests
[error] 178-178: Billing Controller test failed: ReferenceError: AppError is not defined. Failing assertion expects next to be called with expect.any(AppError).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/__tests__/billing.controller.test.js` around lines 178
- 180, Replace the undefined AppError references in the assertions with the
mockAppError used elsewhere: update the two expectations that call
expect.any(AppError) and any direct AppError references (e.g.,
expect(next).toHaveBeenCalledWith(expect.any(AppError)) and
expect(next.mock.calls[0][0].statusCode).../message...) to use mockAppError
instead (e.g., expect.any(mockAppError)), keeping the rest of the checks on next
and next.mock.calls intact so they reference the existing mockAppError symbol.
| console.error("🔥 Unhandled Server Error:", err.stack); | ||
| res.status(500).json({ | ||
| success: false, | ||
| error: statusCode >= 500 ? "Internal Server Error" : message, | ||
| message: message | ||
| error: "Something went wrong!", | ||
| message: err.message |
There was a problem hiding this comment.
Do not expose raw exception messages from the fallback 500 handler.
This branch now sends err.message for every unhandled failure, so internal driver/database/upstream details can leak to clients. Keep the detail in logs and return a fixed public message here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/app.js` around lines 158 - 162, The 500 fallback
handler is leaking internal details by returning err.message; update the
error-response in the Express error middleware (the handler that receives err,
req, res) to remove err.message and return only a fixed public message (e.g.
"Internal Server Error" or the existing "Something went wrong!") and
success:false; keep logging the full error (err.stack) server-side for
diagnostics and, if desired, generate/return a safe errorId or correlation id
instead of the raw exception message.
| if (!month || !/^\d{4}-\d{2}$/.test(month)) { | ||
| return res.status(400).json({ | ||
| success: false, | ||
| data: {}, | ||
| message: 'Provide month as YYYY-MM (e.g. 2026-05)', | ||
| }); | ||
| return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)')); |
There was a problem hiding this comment.
Reject months outside 01-12.
The current regex accepts values like 2026-00 and 2026-13, and Date.UTC() will silently normalize those into a different month instead of rejecting them. That makes this endpoint return valid-looking cohort data for the wrong period.
Suggested fix
- if (!month || !/^\d{4}-\d{2}$/.test(month)) {
+ if (!month || !/^\d{4}-(0[1-9]|1[0-2])$/.test(month)) {
return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)'));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!month || !/^\d{4}-\d{2}$/.test(month)) { | |
| return res.status(400).json({ | |
| success: false, | |
| data: {}, | |
| message: 'Provide month as YYYY-MM (e.g. 2026-05)', | |
| }); | |
| return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)')); | |
| if (!month || !/^\d{4}-(0[1-9]|1[0-2])$/.test(month)) { | |
| return next(new AppError(400, 'Provide month as YYYY-MM (e.g. 2026-05)')); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js` around lines
115 - 116, The current month validation in the controller (the if that checks
month and uses /^\d{4}-\d{2}$/) allows invalid months like 00 or 13; update that
check to reject months outside 01–12 by using a stricter pattern (e.g.
/^\d{4}-(0[1-9]|1[0-2])$/) or parse the captured month and assert 1 <= month <=
12 before returning new AppError(400, ...). Locate the validation around the
month variable in admin.metrics.controller.js and replace the regex/condition so
invalid month values trigger the AppError.
| if (cohortSize === 0) { | ||
| return res.json({ | ||
| success: true, | ||
| data: { month, cohortSize: 0, d1: 0, d7: 0, d30: 0 }, | ||
| message: '', | ||
| }); | ||
| return new ApiResponse({ month, cohortSize: 0, d1: 0, d7: 0, d30: 0 }).send(res); |
There was a problem hiding this comment.
Keep the cohort payload shape stable when cohortSize === 0.
This branch drops d1Pct, d7Pct, and d30Pct, but the non-empty path always returns them. Consumers will need a special-case just for empty months, and that tends to break dashboards first.
Suggested fix
- return new ApiResponse({ month, cohortSize: 0, d1: 0, d7: 0, d30: 0 }).send(res);
+ return new ApiResponse({
+ month,
+ cohortSize: 0,
+ d1: 0,
+ d7: 0,
+ d30: 0,
+ d1Pct: 0,
+ d7Pct: 0,
+ d30Pct: 0,
+ }).send(res);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (cohortSize === 0) { | |
| return res.json({ | |
| success: true, | |
| data: { month, cohortSize: 0, d1: 0, d7: 0, d30: 0 }, | |
| message: '', | |
| }); | |
| return new ApiResponse({ month, cohortSize: 0, d1: 0, d7: 0, d30: 0 }).send(res); | |
| if (cohortSize === 0) { | |
| return new ApiResponse({ | |
| month, | |
| cohortSize: 0, | |
| d1: 0, | |
| d7: 0, | |
| d30: 0, | |
| d1Pct: 0, | |
| d7Pct: 0, | |
| d30Pct: 0, | |
| }).send(res); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/controllers/admin.metrics.controller.js` around lines
142 - 143, The early-return when cohortSize === 0 returns an ApiResponse missing
d1Pct, d7Pct, and d30Pct; keep the response shape identical to the non-empty
path by including d1Pct: 0, d7Pct: 0, and d30Pct: 0 alongside month, cohortSize:
0, d1, d7, and d30 in the ApiResponse sent from that branch (the code
constructing the ApiResponse at the cohortSize check).
| module.exports.refreshToken = async (req, res, next) => { | ||
| try { | ||
| const refreshToken = req.cookies.refreshToken; | ||
|
|
||
| if (!refreshToken) { | ||
| return res.status(401).json({ error: "No refresh token provided" }); | ||
| return next(new AppError(401, "No refresh token provided")); | ||
| } | ||
|
|
||
| const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET); | ||
| const user = await Developer.findById(decoded._id).select('+refreshToken'); | ||
|
|
||
| if (!user || user.refreshToken !== refreshToken) { | ||
| return res.status(403).json({ error: "Invalid refresh token" }); | ||
| return next(new AppError(403, "Invalid refresh token")); | ||
| } | ||
|
|
||
| await sendTokenResponse(user, 200, res); | ||
| } catch (err) { | ||
| res.status(403).json({ error: "Invalid or expired refresh token" }); | ||
| next(new AppError(403, "Invalid or expired refresh token")); | ||
| } |
There was a problem hiding this comment.
Do not collapse every refresh-token failure into a 403.
This catch now rewrites JWT errors, database failures, and persistence errors to the same "Invalid or expired refresh token" response. That turns real server faults into misleading auth failures and prevents the global 5xx handler from seeing them. Only map token verification failures here; forward everything else with next(err).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/controllers/auth.controller.js` around lines 591 -
609, The refreshToken controller currently maps all exceptions to a 403 "Invalid
or expired refresh token"; change the error handling so only JWT verification
errors are converted to an AppError(403) and all other errors are forwarded to
the global handler with next(err). Specifically, in module.exports.refreshToken
around jwt.verify and Developer.findById, detect jwt errors (e.g. err.name ===
'TokenExpiredError' or 'JsonWebTokenError') and call next(new AppError(403,
"Invalid or expired refresh token")), but for any other error (DB lookup,
persistence, unexpected exceptions) call next(err) so they surface as 5xx; keep
references to jwt.verify, Developer.findById, sendTokenResponse, AppError, and
next when implementing this conditional catch behavior.
| ports: | ||
| - '27017:27017' |
There was a problem hiding this comment.
Do not publish MongoDB and Redis to the host by default.
These new port mappings expose both datastores outside the Compose network, which is a substantial security regression for a file that also sets NODE_ENV: production. If host access is only for local debugging, gate it behind a dev-only override or bind to 127.0.0.1 instead.
Also applies to: 18-19
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docker-compose.yml` around lines 8 - 9, The docker-compose.yml currently
publishes datastore ports (ports: '27017:27017' and similarly '6379:6379') to
the host while NODE_ENV: production is set; remove or restrict these host-facing
port mappings for the Mongo/Redis services: either delete the ports entries so
they remain internal to the Compose network, or change them to bind to localhost
(e.g., 127.0.0.1:27017:27017 and 127.0.0.1:6379:6379), and if host access is
needed for local debugging move the host-published mappings into a dev-only
override file (docker-compose.override.yml) instead; update the mongo/redis
service blocks that contain the ports keys and the specific strings
'27017:27017' and '6379:6379'.
| const hasEmail = usersCollection.model.find(f => f.key === 'email' && f.type === 'String' && f.required); | ||
| const hasPassword = usersCollection.model.find(f => f.key === 'password' && f.type === 'String' && f.required); | ||
|
|
||
| if (!hasEmail || !hasPassword) { | ||
| return res.status(422).json({ | ||
| error: "Invalid Users Schema", | ||
| message: "The 'users' collection is missing required 'email' and 'password' string fields. Please fix the schema in the dashboard." | ||
| }); | ||
| return next(new AppError(422, "The 'users' collection is missing required 'email' and 'password' string fields. Please fix the schema in the dashboard.", "Invalid Users Schema")); |
There was a problem hiding this comment.
Use the same users-schema normalization here as the dashboard controller.
project.controller.validateUsersSchema() accepts lowercase "string" and string/number truthy required values, but this middleware only accepts exact f.type === 'String' and f.required. A project can therefore pass schema validation when auth is enabled and then have every auth endpoint rejected here. Reuse the same normalization logic before checking email and password.
As per coding guidelines, {packages/common/src/models/**/*.js,apps/public-api/src/controllers/userAuth.controller.js}: "The users schema contract must include: email as required String and password as required String."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/common/src/middleware/checkAuthEnabled.js` around lines 17 - 21, The
middleware currently checks usersCollection.model entries with strict checks
(f.type === 'String' and f.required) which rejects valid schemas that use
lowercase 'string' or truthy numeric/string required flags; update the check in
checkAuthEnabled.js to normalize each field like the dashboard's
project.controller.validateUsersSchema() (treat type case-insensitively, e.g.,
'string' or 'String' as String, and treat required as truthy for '1', 1, 'true',
true, etc.) before testing for email and password presence; specifically
normalize entries from usersCollection.model, then locate the email and password
fields using the normalized.type === 'String' and normalized.required truthiness
and return the same AppError only if either normalized field is missing.
| const { projectId } = req.params; | ||
| if (!projectId) return res.status(400).json({ error: "Project ID is required" }); | ||
| if (!projectId) return next(new AppError(400, "Project ID is required")); | ||
|
|
||
| const project = await Project.findOne({ _id: projectId, owner: req.user._id }); | ||
| if (!project) { | ||
| return res.status(404).json({ error: "Project not found or access denied" }); | ||
| return next(new AppError(404, "Project not found or access denied")); | ||
| } | ||
|
|
||
| req.project = project; | ||
| next(); | ||
| } catch (err) { | ||
| res.status(500).json({ error: err.message }); | ||
| next(new AppError(500, err.message, "Internal Server Error")); |
There was a problem hiding this comment.
Validate projectId before querying, and don't echo err.message back to clients.
An invalid Mongo ObjectId will throw here, and the catch path currently turns that into a 500 whose message is the raw driver error. That leaks internals and misclassifies a bad request as a server failure. Validate the id up front, then fall back to a stable generic message in the catch block.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/common/src/middleware/loadProjectForAdmin.js` around lines 7 - 18,
In the loadProjectForAdmin middleware, validate projectId (use
mongoose.Types.ObjectId.isValid or equivalent) before calling Project.findOne
and return next(new AppError(400, "Invalid project ID")) for an invalid id; then
keep the existing owner-restricted query (Project.findOne({_id: projectId,
owner: req.user._id})) and assignment to req.project. In the catch block, stop
echoing err.message to clients—call next(new AppError(500, "Internal Server
Error")) and log the original err internally (e.g., console.error or req.logger)
instead of returning err.message.
| if (body && typeof body === 'object') { | ||
| if (body.error && typeof body.error === 'string') errorTitle = body.error; | ||
| if (body.message && typeof body.message === 'string') errorMessage = body.message; | ||
|
|
||
| // If it only had error and no message, use error as message | ||
| if (body.error && !body.message) { | ||
| errorTitle = 'Error'; | ||
| errorMessage = body.error; | ||
| } |
There was a problem hiding this comment.
Do not overwrite a parsed string message with raw body.error objects/arrays.
toErrorMessage(body) already reduces validation payloads to a string, but this new fallback replaces it with body.error whenever message is missing. For shapes like { error: [{ message: '...' }] }, clients now get a non-string message.
Suggested fix
- if (body.error && !body.message) {
- errorTitle = 'Error';
- errorMessage = body.error;
+ if (typeof body.error === 'string' && !body.message) {
+ errorTitle = body.error;
+ errorMessage = body.error;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (body && typeof body === 'object') { | |
| if (body.error && typeof body.error === 'string') errorTitle = body.error; | |
| if (body.message && typeof body.message === 'string') errorMessage = body.message; | |
| // If it only had error and no message, use error as message | |
| if (body.error && !body.message) { | |
| errorTitle = 'Error'; | |
| errorMessage = body.error; | |
| } | |
| if (body && typeof body === 'object') { | |
| if (body.error && typeof body.error === 'string') errorTitle = body.error; | |
| if (body.message && typeof body.message === 'string') errorMessage = body.message; | |
| // If it only had error and no message, use error as message | |
| if (typeof body.error === 'string' && !body.message) { | |
| errorTitle = body.error; | |
| errorMessage = body.error; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/common/src/middleware/standardizeApiResponse.js` around lines 70 -
78, The current fallback replaces a parsed string message from
toErrorMessage(body) with raw body.error objects/arrays; update the conditional
logic in the standardizeApiResponse middleware so you only assign
errorTitle/errorMessage from body.error when body.error is a string (i.e., keep
the existing checks `if (body.error && typeof body.error === 'string')` and do
not set errorMessage from body.error when it is an object/array), and ensure the
previously computed toErrorMessage(body) result remains the message when
body.error is non-string; reference the variables and helpers errorTitle,
errorMessage, body.error and the toErrorMessage function to locate and change
the fallback logic.
|
Hi @yash-pouranik , |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/public-api/src/__tests__/userAuth.social.test.js (1)
449-473:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winCritical: Social refresh exchange is not atomic (get+del race)
The controller consumes the one-time exchange code via separateredis.get(exchangeKey)andredis.del(exchangeKey)calls, instead of atomicGETDEL, violating the guideline forproject:social-auth:refresh-exchange:{rtCode}. This allows concurrent requests to reuse the samertCode.🔒 Required fix: Use atomic GETDEL
- redis.get.mockResolvedValueOnce(JSON.stringify({ + redis.getdel.mockResolvedValueOnce(JSON.stringify({ token: 'issued_access_token', refreshToken: 'issued_refresh_token', })); const req = makeReq(); req.body = { rtCode: 'code_123', token: 'issued_access_token', }; const res = makeRes(); await controller.exchangeSocialRefreshToken(req, res, next); - expect(redis.get).toHaveBeenCalledWith('project:social-auth:refresh-exchange:code_123'); - expect(redis.del).toHaveBeenCalledWith('project:social-auth:refresh-exchange:code_123'); + expect(redis.getdel).toHaveBeenCalledWith('project:social-auth:refresh-exchange:code_123'); expect(res.json).toHaveBeenCalledWith({ success: true, data: { refreshToken: 'issued_refresh_token', }, message: 'Refresh token exchanged successfully', });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/userAuth.social.test.js` around lines 449 - 473, The controller exchangeSocialRefreshToken currently does separate redis.get and redis.del calls causing a race; change controller.exchangeSocialRefreshToken to perform an atomic GETDEL (use your Redis client’s getDel/GETDEL command) against the same key 'project:social-auth:refresh-exchange:{rtCode}', remove the separate del call, and update the test to mock and expect a single redis.getDel (or getdel) call returning the JSON payload and assert res.json as before so the exchange is atomic.
🧹 Nitpick comments (1)
apps/public-api/src/__tests__/userAuth.social.test.js (1)
199-219: ⚡ Quick winStrengthen OAuth state Redis assertions in startSocialAuth tests
startSocialAuthstores OAuth state using keyproject:auth:oauth:state:${state}and sets TTL to600(EX), but the tests only assertredis.setwas called. Add assertions for the key pattern and TTL to prevent regressions.Suggested change
await controller.startSocialAuth(req, res, next); -expect(redis.set).toHaveBeenCalled(); +expect(redis.set).toHaveBeenCalledWith( + expect.stringMatching(/^project:auth:oauth:state:[a-f0-9]+$/), + expect.any(String), + 'EX', + 600 +); expect(res.redirect).toHaveBeenCalledWith(expect.stringContaining('https://github.com/login/oauth/authorize?'));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/public-api/src/__tests__/userAuth.social.test.js` around lines 199 - 219, Update the two startSocialAuth tests to assert the redis.set call includes the OAuth state key pattern and TTL: verify redis.set was called with a key that contains "project:auth:oauth:state:" (the dynamic state suffix may vary), the stored value (state/token), and the expiration flags ('EX' and 600) in addition to the existing redirect assertions; reference the controller.startSocialAuth and redis.set invocation so the tests fail if the key format or TTL changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/public-api/src/__tests__/userAuth.social.test.js`:
- Around line 449-473: The controller exchangeSocialRefreshToken currently does
separate redis.get and redis.del calls causing a race; change
controller.exchangeSocialRefreshToken to perform an atomic GETDEL (use your
Redis client’s getDel/GETDEL command) against the same key
'project:social-auth:refresh-exchange:{rtCode}', remove the separate del call,
and update the test to mock and expect a single redis.getDel (or getdel) call
returning the JSON payload and assert res.json as before so the exchange is
atomic.
---
Nitpick comments:
In `@apps/public-api/src/__tests__/userAuth.social.test.js`:
- Around line 199-219: Update the two startSocialAuth tests to assert the
redis.set call includes the OAuth state key pattern and TTL: verify redis.set
was called with a key that contains "project:auth:oauth:state:" (the dynamic
state suffix may vary), the stored value (state/token), and the expiration flags
('EX' and 600) in addition to the existing redirect assertions; reference the
controller.startSocialAuth and redis.set invocation so the tests fail if the key
format or TTL changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 378590b4-d5cc-48fc-ad31-596300742d2d
📒 Files selected for processing (7)
apps/dashboard-api/src/__tests__/billing.controller.test.jsapps/dashboard-api/src/controllers/project.controller.jsapps/public-api/src/__tests__/mail.controller.test.jsapps/public-api/src/__tests__/userAuth.refresh.test.jsapps/public-api/src/__tests__/userAuth.social.test.jsapps/public-api/src/controllers/data.controller.jsapps/public-api/src/controllers/userAuth.controller.js
💤 Files with no reviewable changes (2)
- apps/public-api/src/controllers/data.controller.js
- apps/dashboard-api/src/controllers/project.controller.js
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/dashboard-api/src/tests/billing.controller.test.js
- apps/public-api/src/tests/userAuth.refresh.test.js
- apps/public-api/src/tests/mail.controller.test.js
|
Please fix all the comments by coderabbitai |
|
Closing the PR as It will be divided in 5 small PR for easy review |
|
Thank you so much Man 😭 |
🚀 Pull Request Description
I had added
AppErrorandAppResponseUtility to entire backend's middleware & controller which provide a standard way to operate. Migrated raw JSON response with standard utility.I had also updated corresponding test to match the standard response
Fixes #237
🛠️ Type of Change
🧪 Testing & Validation
Backend Verification:
npm testin thebackend/directory and all tests passed.Frontend Verification:
npm run lintin thefrontend/directory.✅ Checklist
Summary by CodeRabbit
Bug Fixes
New Features
Chores
Infrastructure